package dao

import (
	"context"
	"database/sql"
	"errors"
	"github.com/go-sql-driver/mysql"
	"gorm.io/gorm"
	"time"
)

// 预定义错误
var (
	ErrDuplicateEmail = errors.New("该邮箱已被注册")
	ErrRecordNotFound = gorm.ErrRecordNotFound
)

type UserDAO interface {
	Insert(ctx context.Context, user User) error
	FindByEmail(ctx context.Context, email string) (User, error)
	UpdateById(ctx context.Context, user User) error
	FindById(ctx context.Context, id int64) (User, error)
	FindByPhone(ctx context.Context, phone string) (User, error)
	FindByWechat(ctx context.Context, openID string) (User, error)
}

// GORMUserDAO GORM 实现
type GORMUserDAO struct {
	db *gorm.DB
}

func NewUserDAO(db *gorm.DB) UserDAO {
	return &GORMUserDAO{db: db}
}

// User 这里要定义自己的User，而非使用domain.User
// 因为会增加ctime、utime字段
type User struct {
	Id int64 `gorm:"primaryKey,autoIncrement"`
	// 设置唯一索引
	Email sql.NullString `gorm:"unique"`
	// NullString: 代表这里是一个可以为 NULL 的列
	Phone sql.NullString `gorm:"unique"`
	// 还有一种写法，只不过指针如果为nil，解引用会panic，需要加一个if判断
	//Phone    *string `gorm:"unique"`
	Password string
	Nickname string `gorm:"size:36"`
	Birthday int64
	AboutMe  string `gorm:"size:200"`

	// 此时我们还需要考虑一下索引的设计
	// 1. 如果查询时同时要求使用 openid 和 unionid，就要创建联合唯一索引
	// 2. 如果查询只用 openid，就只需要再 openid 上创建一个唯一索引 或者 <openid,unionid> 联合索引
	// 3. 如果查询只用 unionid，就只需要再 unionid 上创建一个唯一索引 或者 <unionid,openid> 联合索引
	WechatOpenId  sql.NullString `gorm:"unique"`
	WechatUnionId sql.NullString

	// 创建时间
	Ctime int64
	// 更新时间
	Utime int64
}

// Insert 这边的命名就才去非常接近数据库的语义
func (dao *GORMUserDAO) Insert(ctx context.Context, user User) error {
	now := time.Now().UnixMilli()
	user.Ctime = now
	user.Utime = now
	err := dao.db.WithContext(ctx).Create(&user).Error
	if mysqlError, ok := err.(*mysql.MySQLError); ok {
		// 如果你不知道对应的错误码是什么，可以在你打印的日志里看到
		const uniqueIndexError uint16 = 1062
		// 说明是唯一索引冲突，即邮箱冲突
		if mysqlError.Number == uniqueIndexError {
			/*
				TODO: 注意，后续加入其他唯一索引后（比如手机号），之类的处理就需要修改了，
				 到时候在进行重构这部分
			*/
			return ErrDuplicateEmail
		}
	}
	return err
}

// FindByEmail 根据邮箱查询用户
func (dao *GORMUserDAO) FindByEmail(ctx context.Context, email string) (User, error) {
	var user User
	err := dao.db.WithContext(ctx).Where("email=?", email).First(&user).Error
	return user, err
}

// UpdateById 修改个人信息
func (dao *GORMUserDAO) UpdateById(ctx context.Context, user User) error {
	return dao.db.WithContext(ctx).Where("id=?", user.Id).Updates(&user).Error
}

// FindById 根据ID查询
func (dao *GORMUserDAO) FindById(ctx context.Context, id int64) (User, error) {
	var user User
	err := dao.db.WithContext(ctx).Where("id=?", id).First(&user).Error
	return user, err
}

// FindByPhone 根据 Phone 查询用户
func (dao *GORMUserDAO) FindByPhone(ctx context.Context, phone string) (User, error) {
	var user User
	err := dao.db.WithContext(ctx).Where("phone=?", phone).First(&user).Error
	return user, err
}

// FindByWechat 根据微信的 OpenID 查询用户
func (dao *GORMUserDAO) FindByWechat(ctx context.Context, openId string) (User, error) {
	var user User
	err := dao.db.WithContext(ctx).Where("wechat_open_id=?", openId).First(&user).Error
	return user, err
}
